/*********************************************************************************
 *      Copyright:  (C) 2019 Tang Zhiqiang<t_zhiqiang@163.com>
 *                  All rights reserved.
 *
 *       Filename:  server_epoll.c
 *    Description:  This file is socket server RPI temperature
 *                 
 *        Version:  1.0.0(11/04/2019)
 *         Author:  Tang Zhiqiang <t_zhiqiang@163.com>
 *      ChangeLog:  1, Release initial version on "11/04/2019 08:15:46 PM"
 *                 
 ********************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <time.h>
#include <pthread.h>
#include <getopt.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sqlite3.h>
#include <sys/epoll.h> 
#include "server_socket_init.h"
#include "create_database.h"
#include "crc-itu-t.h"
#include "tlv.h"
#include "print_buf.h"
#include "message_callback.h"
#include "client_list.h"


#define MAX_EVENTS      512
#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))    //计算结构体/数组元素个数
#define database_name    "temper.db"

//void set_socket_rlimit(void);
//int create_database(void);
//int server_socket_init(char *listenip, int listen_port);

int     g_sigstop = 0;
void signal_stop(int signum)
{
    if(SIGTERM == signum)
    {
        printf("SIGTERM signal detected\n");
        g_sigstop = 1;
    }
    return ;
}

static inline void print_usage(char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is a socket server program, which used to verify client and echo back string from it\n",progname);
    printf("\nMandatory arguments to long options are mandatory for short options too:\n");
    printf(" -b[daemon ] set program running on background\n");
    printf(" -p[port ] Socket server port address\n");
    printf(" -h[help ] Display this help information\n");
    printf("\nExample: %s -b -p 8900\n", progname);
    return ;
}


int main(int argc, char *argv[])
{
    int             listenfd,connfd;
    int             serv_port = 0;
    int             daemon_run = 0;
    char            *progname = NULL;
    int             opt;
    int             rv;
    int             log_fd;
    int             i;
    //char            sql1[128];
    //char            id_buf[30];
    //char            time_buf[50];
    //char            temper_buf[10];
    analy_data      total_data;     //解析成功后总数据
    int             epollfd;
    int             events;
    struct epoll_event event;
    struct epoll_event event_array[MAX_EVENTS];
    //char            *zErrMsg = NULL;
    sqlite3         *db;
    int             len;
    int             data_flag;      //标志三个tlv全都收到数据
    unsigned short  crc16;
    unsigned short  crc_val;
    //struct tlv_total    data_p;
    //int             offset=0;                 //下一次从链接的fd中读取到buf时的起始地址
    //tlv_s           *zero_p=NULL; 
    //union_s       *union_t=NULL;
    //struct data_flag_s data_flag={0};   //用于标识三个TLV都成功解析数 
    client_list_t   *cur_client_node;
    client_list_t   *new_client_node;
    int             n;

    struct option long_options[]=
    {
        {"daemon", no_argument, NULL, 'd'},
        {"port", required_argument, NULL, 'p'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };

    progname = basename(argv[0]);

    //Parser the command line parameters 
    while((opt = getopt_long(argc, argv, "dp:h", long_options, NULL)) != -1)
    {
        switch (opt) 
        {
            case 'd':
                daemon_run = 1;
                break;
            case 'p':
                serv_port = atoi(optarg);
                break;
            case 'h':
                print_usage(progname);
                break;
            default:
                break;
        }
    }
    if(!serv_port) 
    {
        print_usage(progname);
        return -1;
    }

    signal(SIGTERM, signal_stop);   //15号信号（SIGTERM）可被捕捉

    if(daemon_run)
    {
        printf("program %s running at the backgroud now\n", argv[0]);

        //建立日志系统
        log_fd=open("receive_temper.log", O_CREAT|O_RDWR, 0666);
        if(log_fd < 0) 
        {
            printf("Open the logfile failure: %s\n", strerror(errno));
            return 0;
        }
        //标准输出、标准出错重定向
        dup2(log_fd, STDOUT_FILENO);
        dup2(log_fd, STDERR_FILENO);

        //程序后台运行
        if(daemon(1, 1) <0 )
        {
            printf("Deamon failure: %s", strerror(errno));
            return 0;
        }
    }

    if((listenfd=server_socket_init(NULL, serv_port)) < 0)
    {
        printf("ERROR: %s server listen on port %d failure.\n", argv[0], serv_port);
        return -2;
    }
    printf("server socket[%d] start to listen on port %d\n", listenfd, serv_port);

    printf("Start create database.\n");
    //查看数据库是否已存在
    if (access(database_name, W_OK) < 0)
    {
        if(rv=create_database() < 0)
        {
            printf("Create database failure: %s\n", strerror(errno));
            return -1;
        }  
    }
    printf("Create database OK and opened database.\n");

    /*   Open database  */
    len = sqlite3_open(database_name, &db);     //最后不用一定要关闭
    if(len != SQLITE_OK)
    {
        sqlite3_close(db);      
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));   
    }
    printf("Opened database successfully\n");


    /*  将加入 listenfd 到兴趣数组中 */
    if((epollfd=epoll_create(MAX_EVENTS)) < 0)       //这里缺少括号，优先级出问题了 已改
    {
        printf("%s create epoll farilure: %s\n", argv[0], strerror(errno));
        return -3;
    }
    printf("programe create epoll[%d] OK.\n", epollfd);
    /* 必须将文件描述符赋值给event.data.fd */
    event.events = EPOLLIN; 
    event.data.fd = listenfd;
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0 )
    {
        printf("epoll add listen socket[%d] failure: %s\n", listenfd, strerror(errno));
        return -4;
    }
    /* 创建链表，保存客户端fd和buf等 */
    client_list_t *head = create_list();     //创建带头节点的链表
    print_test;
    if (head==NULL)
    {        
        print_test;
        return -1;  
    } 

    //printf("programe will start running...\n");
    while(!g_sigstop)
    {  

        //继续遍历epoll数组当中fd
        //program will blocked here
        printf("programe will blocked here...\n");
        events=epoll_wait(epollfd, event_array, MAX_EVENTS, -1);    //成功则返回数据元素个数 (epoll为阻塞模式)
        if(events < 0)
        {
            printf("epoll failure: %s\n", strerror(errno));
            break;
        }
        else if(events==0) 
        {
            printf("epoll timeout\n");
            continue;
        }
        //events>0 is the active events count
        for(i = 0; i < events; i++)
        {   
            //for循环的events与结构体成员中的events不同
            //按位与求的是结果，逻辑与求的是真或假
            if((event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP)) 
            {
                printf("epoll_wait get error on fd[%d]: %s\n",event_array[i].data.fd, strerror(errno));
                //在epoll兴趣数组中，出现错误client的fd，删除结点
                delete_node(head, event_array[i].data.fd);                      //删除节点需要传一级指针
                epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);//在epoll数组中删除fd
                close(event_array[i].data.fd);                                  //是否需要删除链表
            }
            //listen socket get event means new client start connect now
            if(event_array[i].data.fd == listenfd) //已将listenfd加入到epoll感兴趣事件集合
            {
                if((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0) //不保存客户端信息（结构体指针为NULL）
                {
                    printf("accept new client failure: %s\n", strerror(errno));      
                    continue;
                }
                event.data.fd = connfd; 
                event.events = EPOLLIN;
                if(epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event) < 0 )
                {
                    printf("epoll add client socket failure: %s\n", strerror(errno));
                    close(event_array[i].data.fd);
                    continue;
                }
                printf("accept new client[%d] OK\n", connfd);   
                
                //为每个新客户端创建结构体链表，插入到链表尾
                new_client_node = add_list_to_tail(head, connfd);    //尾插法,传入头指针的地址
                if (new_client_node == NULL)
                {   
                    //客户端链表创建失败，重新轮询epoll数组
                    printf("Insert list tail node error.\n");
                    continue;
                }
                //每次新加入的客户端链表，需要初始化链表中的节点
                //memset(new_client_node, 0, sizeof(client_list_t) );           //里面含义指针，不能全部清零
                memset(new_client_node->buf, 0, sizeof(new_client_node->buf) ); //服务器读取客户端数据的buf
                memset((void *)&new_client_node->data_p, 0, sizeof(struct tlv_total) );  //解析成功的id，time等TLV数据
                new_client_node->offset = 0;                                    //buf剩余数据偏移量
                continue;       //结束本次循环，继续遍历epoll数组（防止if判断之外有其他代码执行导致循环错误）
            }
            else //data arriver from alrady connected client
            {  
                printf("data arriver from alrady connected client fd[%d]\n", event_array[i].data.fd);
                cur_client_node = find_node(head, event_array[i].data.fd); 
                if (cur_client_node == NULL)
                {
                    print_test;
                    //客户端fd出错，不在链表当中，直接关闭该客户端连接
                    printf("client list no current client.\n");
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                    close(event_array[i].data.fd);
                    continue;
                }
                print_test;
                //找到fd对应的节点
                //print_buf(cur_client_node->buf, cur_client_node->offset);       //二次连接服务读取数据为空
                //对端/本端调用shutdown/close，则read返回值等于0
                if((rv=read(cur_client_node->fd, &cur_client_node->buf[cur_client_node->offset], 
                                sizeof(cur_client_node->buf)-sizeof(cur_client_node->offset)) ) <= 0 )  
                {
                    //读取最多应为剩余buf大小
                    printf("socket[%d] read data failure or disconnect and will be remove.\n", event_array[i].data.fd);
                    //client的fd错误或断开，删除结点
                    delete_node(head, event_array[i].data.fd);   //删除节点需要传一级指针
                    cur_client_node = NULL;

                    epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                    close(event_array[i].data.fd);
                    continue;      //结束本次循环 for(i = 0; i < events; i++)
                }
                //读取TLV数据成功，开始解析TLV格式数据包
                printf("socket[%d] read %02x byte data.\n", event_array[i].data.fd, rv);
                print_test;
                cur_client_node->offset += rv;       //buf剩余数据是本次读取数据加上上次剩余数据
                //从buf中读取数据存放在结构成员数组中  
                printf("Start unpack TLV data.\n");
                //传入数据大小和tlv数据的标识地址，需要更改数据大小
                rv = tlv_unpack(cur_client_node->buf, &cur_client_node->offset, &cur_client_node->data_p, &data_flag);

                //解析TLV出错，返回值为-1，请求重发数据
                if (rv < 0)
                {
                    printf("Unpack TLV data failure.\n");
                    printf("socket[%d] read data failure and send NAK to client.\n", event_array[i].data.fd);
                    //printf("%d\n", __LINE__);
                    message_callback(event_array[i].data.fd, (char )Tag_nak,  nak);
                    continue;
                }
                //解析数据成功，剩余数据大小为cur_client_node->offset
                //判断三个tlv是否都已解析完成
                if (data_flag != 7)
                {
                    printf("id time and temperature TLV unpack not full.\n");
                    //message_callback(event_array[i].data.fd, (char )Tag_nak,  nak);
                    continue;
                }
                print_test;

                printf("Unpack TLV data succeed.\n");
                
                //获取id值，以字符串的形式写入到buf中 
                snprintf(total_data.id_buf, sizeof(total_data.id_buf), "RPI%02d", cur_client_node->data_p.id[0]);
                //获取时间数据值
                snprintf(total_data.time_buf, sizeof(total_data.time_buf), "%d-%02d-%02d %02d:%02d:%02d", 
                  cur_client_node->data_p.time[0]+2000, cur_client_node->data_p.time[1], cur_client_node->data_p.time[2], 
                  cur_client_node->data_p.time[3], cur_client_node->data_p.time[4], cur_client_node->data_p.time[5]);
                //获取温度数据值
                snprintf(total_data.temper_buf, sizeof(total_data.temper_buf), "%d.%dC", 
                        cur_client_node->data_p.temper[0], cur_client_node->data_p.temper[1]);
                
                print_test;
                //while循环之前已打开数据库，直接插入数据
                //printf("client fd = %d\n", event_array[i].data.fd);
                rv = insert_database(db, &total_data);
                if (rv < 0)     //判断返回值，如果不等于SQLITE_OK，即插入记录失败            
                {
                    printf("insert data failure.\n");                                  
                    //发送NAK，请求重新发送TLV数据包
                    message_callback(event_array[i].data.fd, (char )Tag_nak,  nak);
                    //epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                    //close(event_array[i].data.fd);
                    continue;
                }

                printf("data save to database successfully!\n");
                //发生ACK确认报文给客户端, 已成功保存到数据库,可以进行下一次数据传输
                //epoll默认采用水平触发(可写时,每次都提醒), 阻塞和非阻塞都是一样效果,在不可写时,write函数会阻塞   
                message_callback(event_array[i].data.fd, Tag_ack, ack);

                //数据库保存完成，需要初始化链表中的节点，防止下一次传输数据不完成，部分数据未更新
                memset(new_client_node->buf, 0, sizeof(new_client_node->buf) );           //服务器读取客户端数据的buf
                //解析成功的id，time等TLV数据               
                memset((void *)&new_client_node->data_p, 0, sizeof(struct tlv_total) );              
                new_client_node->offset = 0;            //buf剩余数据偏移量
                
                printf("TLV data storage to database and initialize client list data.\n");

            }//data arriver from alrady connected client 
        }//for(i = 0; i < events; i++)
    }//while(!g_sigstop)  

    destory_list(&head);  //销毁整个链表
    close(listenfd);    
    sqlite3_close(db);  //关闭数据库

    return 0;
}//main

